// Copyright 2017 Baidu Inc. All Rights Reserved.
// Author: Su Hao (suhao@baidu.com)
//
// Description: The network adapter.

#include "baidu_ca_transport.h"
#include "baidu_ca_internal.h"
#include "baidu_ca_trans_wrapper.h"
#include "baidu_ca_trans_encrypted.h"
#include "duer_log.h"

enum _baidu_ca_trans_type_enum {
    BCA_TRANS_NORMAL,
    BCA_TRANS_SECURE,
    BCA_TRANS_TOTAL
};

typedef struct _bca_transport_callbacks {
    bca_status_t (*f_connect)(bca_trans_ptr, const bca_addr_t*);
    bca_status_t (*f_send)(bca_trans_ptr, const void*, bca_size_t, const bca_addr_t*);
    bca_status_t (*f_recv)(bca_trans_ptr, void*, bca_size_t, bca_addr_t*);
    bca_status_t (*f_close)(bca_trans_ptr);
} bca_trans_cbs;

const BCA_LOC bca_trans_cbs s_bca_trans_callbacks[BCA_TRANS_TOTAL] = {
    // BCA_TRANS_NORMAL
    {
        bca_trans_wrapper_connect,
        bca_trans_wrapper_send,
        bca_trans_wrapper_recv,
        bca_trans_wrapper_close
    },
    // BCA_TRANS_SECURE
    {
        bca_trans_encrypted_connect,
        bca_trans_encrypted_send,
        bca_trans_encrypted_recv,
        bca_trans_encrypted_close
    },
};

BCA_LOC_IMPL bca_u8_t bca_trans_is_encrypted(bca_trans_ptr ptr) {
    return ptr && ptr->cert && ptr->cert_len > 0;
}

BCA_LOC const bca_trans_cbs* bca_trans_get_callbacks(bca_trans_ptr ptr) {
    if (ptr) {
        if (bca_trans_is_encrypted(ptr)) {
            return &s_bca_trans_callbacks[BCA_TRANS_SECURE];
        } else {
            return &s_bca_trans_callbacks[BCA_TRANS_NORMAL];
        }
    }

    return NULL;
}

BCA_INT_IMPL bca_trans_handler bca_trans_acquire(bca_context context) {
    bca_trans_ptr trans = BCA_MALLOC(sizeof(bca_trans_t));

    if (trans) {
        BCA_MEMSET(trans, 0, sizeof(bca_trans_t));
        trans->soc_context = context;
        bca_trans_wrapper_create(trans);
        trans->read_timeout = BCA_READ_FOREVER;
    }

    DUER_LOGD("bca_trans_acquire: trans = %x", trans);
    return trans;
}

BCA_INT_IMPL bca_status_t bca_trans_set_pk(bca_trans_handler hdlr,
                                           const void* data,
                                           bca_size_t size) {
    bca_trans_ptr trans = (bca_trans_ptr)hdlr;

    if (trans) {
        if (trans->cert) {
            BCA_FREE(trans->cert);
        }

        trans->cert = BCA_MALLOC(size);

        if (trans->cert) {
            BCA_MEMCPY(trans->cert, data, size);
            trans->cert_len = size;
        } else {
            trans->cert_len = 0;
        }
    }

    return BCA_NO_ERR;
}

BCA_INT_IMPL bca_status_t bca_trans_set_read_timeout(bca_trans_handler hdlr,
                                                     bca_u32_t timeout) {
    bca_trans_ptr trans = (bca_trans_ptr)hdlr;

    if (trans) {
        trans->read_timeout = timeout;

        if (bca_trans_is_encrypted(trans)) {
            bca_trans_encrypted_set_read_timeout(hdlr, timeout);
        }

        return BCA_NO_ERR;
    }

    return BCA_ERR_INTERNAL;
}

BCA_INT_IMPL bca_status_t bca_trans_connect(bca_trans_handler hdlr,
                                            const bca_addr_t* addr) {
    bca_status_t rs = BCA_ERR_INTERNAL;
    bca_trans_ptr trans = (bca_trans_ptr)hdlr;

    if (trans) {
        trans->addr.type = addr->type;
    }

    const bca_trans_cbs* cbs = bca_trans_get_callbacks(trans);

    if (cbs) {
        rs = cbs->f_connect(trans, addr);
    }

    return rs;
}

BCA_INT_IMPL bca_status_t bca_trans_send(bca_trans_handler hdlr,
                                         const void* data,
                                         bca_size_t size,
                                         const bca_addr_t* addr) {
    bca_status_t rs = BCA_ERR_INTERNAL;
    bca_trans_ptr trans = (bca_trans_ptr)hdlr;
    const bca_trans_cbs* cbs = bca_trans_get_callbacks(trans);

    if (cbs) {
        rs = cbs->f_send(trans, data, size, addr);
    }

    return rs;
}

BCA_INT_IMPL bca_status_t bca_trans_recv(bca_trans_handler hdlr,
                                         void* data,
                                         bca_size_t size,
                                         bca_addr_t* addr) {
    bca_status_t rs = BCA_ERR_INTERNAL;
    bca_trans_ptr trans = (bca_trans_ptr)hdlr;
    const bca_trans_cbs* cbs = bca_trans_get_callbacks(trans);

    if (cbs) {
        rs = cbs->f_recv(trans, data, size, addr);
    }

    return rs;
}

BCA_INT_IMPL bca_status_t bca_trans_close(bca_trans_handler hdlr) {
    bca_status_t rs = BCA_ERR_INTERNAL;
    bca_trans_ptr trans = (bca_trans_ptr)hdlr;
    const bca_trans_cbs* cbs = bca_trans_get_callbacks(trans);

    if (cbs) {
        rs = cbs->f_close(trans);
    }

    return rs;
}

BCA_INT_IMPL bca_status_t bca_trans_release(bca_trans_handler hdlr) {
    bca_trans_ptr trans = (bca_trans_ptr)hdlr;

    if (trans) {
        bca_trans_close(hdlr);

        if (trans->cert) {
            BCA_FREE(trans->cert);
            trans->cert = NULL;
            trans->cert_len = 0;
        }

        bca_trans_wrapper_destroy(trans);
        DUER_LOGD("bca_trans_release: trans = %x", trans);
        BCA_FREE(trans);
    }

    return BCA_NO_ERR;
}
